using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Acms.SqlDal;
using System.Security.Cryptography;

namespace Acms.Bll
{
    public class UserSecurity : User
    {
        #region Construtors
        protected UserSecurity()
            : base()
        {
        }

        protected UserSecurity(Hashtable table)
            : base(table)
        {
        }

        protected UserSecurity(Hashtable table, List<UserSecurity> collection)
            : base(table, collection)
        {
        }
        #endregion

        #region Static Getters
        private static List<UserSecurity> LoadAll(Hashtable[] tables)
        {
            List<UserSecurity> tmp = new List<UserSecurity>();
            if (tables != null)
            {
                foreach (Hashtable table in tables)
                {
                    tmp.Add(new UserSecurity(table, tmp));
                }
            }
            return tmp;
        }

        private static UserSecurity LoadItem(Hashtable table)
        {
            if (table != null)
            {
                return new UserSecurity(table);
            }
            else
            {
                return null;
            }
        }

        protected override Base CreateDal()
        {
            return new UserSecurityDal();
        }

        private new static UserSecurity CreateInstance()
        {
            return new UserSecurity();
        }

        public static UserSecurity CreateInstance(int siteId)
        {
            UserSecurity tmp = new UserSecurity();
            tmp.siteId = siteId;
            return tmp;
        }
      
        public new static UserSecurity GetById(int id)
        {
            UserSecurityDal dal = new UserSecurityDal();
            return LoadItem(dal.GetById(id));
        }
        
        public new static UserSecurity GetByUserName(string username, int siteId)
        {
            UserSecurityDal dal = new UserSecurityDal();
            return LoadItem(dal.GetByUserName(username, siteId));
        }

        public new static UserSecurity GetByEmailAddress(string emailAddress, int siteId)
        {
            UserSecurityDal dal = new UserSecurityDal();
            return LoadItem(dal.GetByEmailAddress(emailAddress, siteId));
        }

        /*
        public new static List<UserSecurity> GetAll()
        {
            UserSecurityDal dal = new UserSecurityDal();
            return LoadAll(dal.GetAll());
        }

        public static List<UserSecurity> GetAllPaged(string sortExpression, int startIndex, int pageSize, ref int totalRecords)
        {
            UserSecurityDal dal = new UserSecurityDal();
            return LoadAll(dal.GetAllPaged(sortExpression, startIndex, pageSize, ref totalRecords));
        }

        public static List<UserSecurity> GetAllByEmailAddressPaged(string emailAddress, string sortExpression, int startRecord, int pageSize, ref int totalRecords)
        {
            UserSecurityDal dal = new UserSecurityDal();
            return LoadAll(dal.GetAllByEmailAddressPaged(emailAddress, sortExpression, startRecord, pageSize, ref totalRecords));
        }

        public static List<UserSecurity> GetAllByUsernamePaged(string userName, string sortExpression, int startRecord, int pageSize, ref int totalRecords)
        {
            UserSecurityDal dal = new UserSecurityDal();
            return LoadAll(dal.GetAllByUsernamePaged(userName, sortExpression, startRecord, pageSize, ref totalRecords));
        }

        public static int CountOnline()
        {
            UserSecurityDal dal = new UserSecurityDal();
            return dal.CountOnline(20);//20 minute session
        }*/
        #endregion

        #region Properties
        protected override void Load(Hashtable table)
        {
            base.Load(table);
            password = (string)table[UserSecurityDal.Fields.Password];
            salt = (int)table[UserSecurityDal.Fields.Salt];
        }

        private string password;

        private int salt;

        private bool locked;
        public bool Locked
        {
            get { return locked; }
        }

        private DateTime? lastLogin;
        public DateTime? LastLogin
        {
            get { return lastLogin; }
        }

        private DateTime? lastActivity;
        public DateTime? LastActivity
        {
            get { return lastActivity; }
        }

        private DateTime? lastChangedPassword;
        public DateTime? LastChangedPassword
        {
            get { return lastChangedPassword; }
        }

        private DateTime? lastLocked;
        public DateTime? LastLocked
        {
            get { return lastLocked; }
        }
        #endregion

        #region CRUD Methods
        public new void Update()
        {
            //set created if not already set
            bool sendRegisteredEmail = false;
            if (IsNewRecord)
            {
                sendRegisteredEmail = true;
                created = DateTime.Now;
            }

            base.Update();

            if (sendRegisteredEmail)
            {
                try
                {
                    Email.SendRegisteredEmail(this, tmpSetPassword);
                }
                catch
                {
                    if (base.TransactionKey != Guid.Empty)
                    {
                        RollbackTransaction();
                    }
                    throw;
                }
            }
        }

        /// <summary>
        /// Sets the created and dosnt send email. Generally used for importing data.
        /// </summary>
        /// <param name="created"></param>
        public void Update(DateTime created)
        {
            if (IsNewRecord)
            {
                this.created = created;
            }
            base.Update();
        }

        protected override void SaveChanges(ref object concurencyId, Base dal)
        {
            //check username unique
            User tmp = User.GetByUserName(this.UserName, this.SiteId);
            if (tmp != null && tmp.Id != this.Id)
            {
                if (this.TransactionKey != Guid.Empty)
                {
                    RollbackTransaction();
                } 
                throw new Acms.Exceptions.DuplicateUsernameException("The username already belongs to another user.");
                
            }
            //check email unique
            tmp = User.GetByEmailAddress(this.EmailAddress, this.SiteId);
            if (tmp != null && tmp.Id != this.Id)
            {
                if (this.TransactionKey != Guid.Empty)
                {
                    RollbackTransaction();
                } 
                throw new Acms.Exceptions.DuplicateEmailException("The email already belongs to another user.");
            }

            //if no transaction and saving cutom property too begin tran
            bool commitTran = false;
            if (CustomPropertiesLoaded && this.TransactionKey == Guid.Empty)
            {
                commitTran = true;
                BeginTransaction(dal);
            }

            ((UserSecurityDal)dal).Update(ref id, siteId, userName, firstName, lastName, dateOfBirth, emailAddress, gender, address1, address2, town, postCode, County, mobileTel, created, password, salt, ref concurencyId);

            if (CustomPropertiesLoaded)
            {
                foreach (UserCustomProperty p in CustomProperties)
                {
                    if (this.TransactionKey != Guid.Empty)
                    {
                        p.UseTransaction(this.TransactionKey);
                    }
                    p.SetUser(this);
                    p.Update();
                }
            }

            if (commitTran)
            {
                CommitTransaction(dal);
            }


        }

        /*public new void Delete()
        {
            base.Delete();
        }

        protected override void DeleteRecord(object concurencyId, Base dal)
        {
            ((UserSecurityDal)dal).Delete(id, concurencyId);
        }*/
        #endregion

        #region Private Methods
        private void CreateSalt()
        {
            Byte[] _saltBytes = new Byte[4];
            RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
            rng.GetBytes(_saltBytes);

            salt = ((((int)_saltBytes[0]) << 24) + (((int)_saltBytes[1]) << 16) + (((int)_saltBytes[2]) << 8) + ((int)_saltBytes[3]));
        }

        //used to hold password for emailing
        private string tmpSetPassword;
        public bool SetPassword(string originalPassword, string newPassword)
        {
            bool retVal = false;
            if (!string.IsNullOrEmpty(password) && string.IsNullOrEmpty(originalPassword))
            {
                throw new ApplicationException("Original password must be supplied if password already set.");
            }
            if (string.IsNullOrEmpty(password) || (AuthenticatePassword(originalPassword) && !AuthenticatePassword(newPassword)))
            {
                lastChangedPassword = DateTime.UtcNow;
                CreateSalt();
                password = ComputeSaltedHash(newPassword);
                retVal = true;
            }
            tmpSetPassword = newPassword;
            return retVal;
        }

        public bool AuthenticatePassword(string password)
        {
            string tmp = ComputeSaltedHash(password);
            return tmp == this.password;
        }

        private string ComputeSaltedHash(string password)
        {
            // Create Byte array of password string
            ASCIIEncoding encoder = new ASCIIEncoding();
            Byte[] secretBytes = encoder.GetBytes(password);

            // Create a new salt
            Byte[] saltBytes = new Byte[4];
            saltBytes[0] = (byte)(salt >> 24);
            saltBytes[1] = (byte)(salt >> 16);
            saltBytes[2] = (byte)(salt >> 8);
            saltBytes[3] = (byte)(salt);

            // append the two arrays
            Byte[] toHash = new Byte[secretBytes.Length + saltBytes.Length];
            Array.Copy(secretBytes, 0, toHash, 0, secretBytes.Length);
            Array.Copy(saltBytes, 0, toHash, secretBytes.Length, saltBytes.Length);

            SHA1 sha1 = SHA1.Create();
            Byte[] computedHash = sha1.ComputeHash(toHash);

            return encoder.GetString(computedHash);
        }
        #endregion


        public string ResetPassword()
        {
            CreateSalt();
            string newPassword = GenerateNewPassword();
            password = ComputeSaltedHash(newPassword);
            return newPassword;
        }

        private string GenerateNewPassword()
        {
            Random seed = new Random();
            Random rnd = new Random(seed.Next());

            char[] alphaChars = new char[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };
            char[] numberic = new char[] { '1', '2', '3', '4', '5', '6', '7', '8', '9' };
            char[] nonAlphNumeric = new char[] { '#', '!', '%', '*' };

            int passwordLength = 10;
            int noNonAlphNumeric = 0;
            int noNumeric = 1;

            int[] passwordFormat = new int[passwordLength];

            int noNumericSet = 0;
            while (noNumericSet < noNumeric)
            {
                int pos = rnd.Next(passwordLength);
                if (passwordFormat[pos] == 0)
                {
                    passwordFormat[pos] = 1;
                    noNumericSet++;
                }
            }
            
            int noNonAlphNumericSet = 0;
            while (noNonAlphNumericSet < noNonAlphNumeric)
            {
                int pos = rnd.Next(passwordLength);
                if (passwordFormat[pos] == 0)
                {
                    passwordFormat[pos] = 2;
                    noNonAlphNumericSet++;
                }
            }

            StringBuilder tmp = new StringBuilder();
            for (int i = 0; i < passwordFormat.Length; i++)
            {
                switch (passwordFormat[i])
                {
                    case 0:
                        tmp.Append(alphaChars[rnd.Next(alphaChars.Length)]);
                        break;
                    case 1:
                        tmp.Append(numberic[rnd.Next(numberic.Length)]);
                        break;
                    case 2:
                        tmp.Append(nonAlphNumeric[rnd.Next(nonAlphNumeric.Length)]);
                        break;
                }
            }
            return tmp.ToString();
        }

        public void Unlock()
        {
            locked = false;
        }

        public List<UserRole> GetRoles(string siteRoot)
        {
            return null;
        }

        public List<UserRole> GetRoles(int siteId)
        {
            return null;
        }

        public void SetLogedOn()
        {
            lastLogin = DateTime.Now;
        }

        public Guid GetLoginKey(bool perminant)
        {
            Guid key = Guid.NewGuid();
            UserSecurityDal dal = new UserSecurityDal();
            dal.CreateLoginKey(key, this.Id, DateTime.Now, perminant);
            return key;
        }

        public void SendForgottenEmail()
        {
            string newPassword = ResetPassword();
            this.Update();
            Email.SendForgottenPasswordEmail(this, newPassword);
        }
    }
}
